﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.Reflection;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Drawing.Imaging;
using System.Configuration;

namespace blackhouse.Web.FasterFiles {
    public class FasterFilesProvider {

        #region Fields

#if DEBUG
        private ICompressOutput cssCompressor = null;
#else
        private ICompressOutput cssCompressor = new CssCompressor();
#endif
        private Page currentPage = null;

        private string eTag;

        private string eTagRequest;

#if DEBUG
        private static ICompressOutput jsCompressor = null;
#else
        private static ICompressOutput jsCompressor = null;// new JSCompressor();
#endif
        //private static Dictionary<Guid, FasterFilesStore> stored = new Dictionary<Guid, FasterFilesStore>();

        private bool useAxdExtensions = true;

        #endregion

        #region Properties

        private string extensionCss { get { return (UseAxdExtensions) ? "css.axd" : "css"; } }

        private string extensionJs { get { return (UseAxdExtensions) ? "js.axd" : "js"; } }

        public bool UseAxdExtensions {
            get {
                if (string.IsNullOrEmpty(ConfigurationManager.AppSettings["FasterFiles_UseAxdExtensions"]))
                    return this.useAxdExtensions;
                else
                    return ConfigurationManager.AppSettings["FasterFiles_UseAxdExtensions"].Equals("true", StringComparison.InvariantCultureIgnoreCase);
            }
            set {
                this.useAxdExtensions = value;
            }
        }

        public bool UseAshxExtensions {
            get {
                if (string.IsNullOrEmpty(ConfigurationManager.AppSettings["FasterFiles_UseAshxExtensions"]))
                    return false;
                else
                    return ConfigurationManager.AppSettings["FasterFiles_UseAshxExtensions"].Equals("true", StringComparison.InvariantCultureIgnoreCase);
            }
        }

        #endregion

        #region Methods

        #region Public

        public void AddCssFile(Type senderType, string file) {
            AddFile(FasterFilesType.Css, senderType, file);
        }

        public void AddFile(FasterFilesType type, Type senderType, string file) {
            this.addFile(type, senderType, file);
        }

        public void AddJsFile(Type senderType, string file) {
            AddFile(FasterFilesType.JavaScript, senderType, file);
        }

        public void GetContent(FasterFilesType type, string id, HttpResponse response) {
            GetContent(type, id, response, null);
        }

        public void GetContent(FasterFilesType type, string id, HttpResponse response, ICompressOutput compressor) {
            this.setETags(type, id);
            this.setHeadersForAll(response, this.eTag);
            if (this.trySetNotModified(response))
                response.End();

            response.Cache.SetAllowResponseInBrowserHistory(true);
            this.trySetGZip();
            string file = getTempFile(type, id);
            if (File.Exists(file)) {
                using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (StreamReader sr = new StreamReader(fs)) {
                    response.Write(sr.ReadToEnd());
                }
            }
        }

        public void GetCssContent(string id, HttpResponse response) {
            GetContent(FasterFilesType.Css, id, response, cssCompressor);
        }

        public void GetImgContent(string assembly, string file, string imageFormat, HttpResponse response) {
            Assembly ass = Assembly.ReflectionOnlyLoad(assembly);
            using (Stream st = ass.GetManifestResourceStream(file + '.' + imageFormat))
            using (MemoryStream mst = new MemoryStream()) {
                if (st != null) {
                    using (Bitmap bp = new Bitmap(st)) {
                        ImageFormat format = null;
                        switch (imageFormat) {
                            case "gif": format = ImageFormat.Gif; break;
                            case "png": format = ImageFormat.Png; break;
                            default: format = ImageFormat.Jpeg; break;
                        }
                        bp.Save(mst, format);
                        mst.WriteTo(response.OutputStream);
                    }
                }
            }
        }

        public void GetJsContent(string id, HttpResponse response) {
            GetContent(FasterFilesType.JavaScript, id, response, jsCompressor);
        }

        #endregion

        #region Private

        private void addCssReference(string id) {
            if (this.currentPage.Header == null)
                return;
            if (string.IsNullOrEmpty(id)) return;
            HtmlLink link = new HtmlLink();
            link.Attributes.Add("rel", "stylesheet");
            link.Attributes.Add("type", "text/css");
            if (this.UseAshxExtensions)
                link.Href = string.Format("/ClientFiles.ashx?type=css&id={0}", id);
            else
                link.Href = string.Format("/{0}/FasterFiles.{1}", id, this.extensionCss);
            this.currentPage.Header.Controls.Add(link);
        }

        private void addFile(FasterFilesType type, Type locationType, string file) {
            if (this.checkHandler()) {
                FasterFilesStore store = this.getStore();
                FasterFilesStoreItem item = new FasterFilesStoreItem();
                item.Type = type;
                item.SenderType = locationType;
                item.File = file;
                if (!store.Contains(item))
                    store.Add(item);
            }
        }

        private void includeBeforeScript(HtmlHead header) {
            this.includeScript(header, this.getBeforeScripts());
        }

        private void includeBeforeScript(ClientScriptManager clientScript) {
            this.includeScript(clientScript, "before", this.getBeforeScripts());
        }

        private void includeAfterScript(HtmlHead header) {
            this.includeScript(header, this.getAfterScripts());
        }

        private void includeAfterScript(ClientScriptManager clientScript) {
            this.includeScript(clientScript, "after", this.getAfterScripts());
        }

        private void includeScript(ClientScriptManager clientScript, string key, List<string> collection) {
            if (collection.Count == 0 || clientScript.IsClientScriptBlockRegistered("faster-files-" + key)) return;

            StringBuilder buildScript = new StringBuilder();
            buildScript.AppendLine("<script type=\"text/javascript\" language=\"javascript\">");
            foreach (string script in collection) {
                if (script.StartsWith("file:")) {
                    buildScript.AppendLine("</script>");
                    buildScript.AppendFormat("<script src=\"{0}\" type=\"text/javascript\" language=\"javascript\"></script>", script.Substring(5));
                    buildScript.AppendLine();
                    buildScript.AppendLine("<script type=\"text/javascript\" language=\"javascript\">");
                } else {
                    buildScript.AppendLine(script);
                }
            }
            buildScript.AppendLine("</script>");
            clientScript.RegisterClientScriptBlock(typeof(FasterFilesProvider), "faster-files-" + key, buildScript.ToString(), false);
        }

        private void includeScript(HtmlHead header, List<string> collection) {
            if (collection.Count == 0) return;

            StringBuilder buildScript = new StringBuilder();
            buildScript.AppendLine("<script type=\"text/javascript\" language=\"javascript\">");
            foreach (string script in collection) {
                if (script.StartsWith("file:")) {
                    buildScript.AppendLine("</script>");
                    buildScript.AppendFormat("<script src=\"{0}\" type=\"text/javascript\" language=\"javascript\"></script>", script.Substring(5));
                    buildScript.AppendLine();
                    buildScript.AppendLine("<script type=\"text/javascript\" language=\"javascript\">");
                } else {
                    buildScript.AppendLine(script);
                }
            }
            buildScript.AppendLine("</script>");
            header.Controls.Add(new Literal() { Text = buildScript.ToString() });
        }

        private void addJsReference(string id) {
            if (string.IsNullOrEmpty(id)) return;
            if (!String.IsNullOrEmpty(HttpContext.Current.Request["jsid"]))
                id = id + "&reft=" + HttpContext.Current.Request["jsid"];
            if (this.currentPage.Header != null && ConfigurationManager.AppSettings["FasterFiles_PrefferScriptManager"] != "true") {
                this.includeBeforeScript(this.currentPage.Header);
                if (this.UseAshxExtensions)
                    this.currentPage.Header.Controls.Add(new Literal() { Text = "<script src=\"" + (HttpContext.Current.Handler as Page).ResolveUrl("~/ClientFiles.ashx") + "?type=js&id=" + id + "\" type=\"text/javascript\"></script>" });
                else
                    this.currentPage.Header.Controls.Add(new Literal() { Text = "<script src=\"/" + id + "/FasterFiles." + this.extensionJs + "\" type=\"text/javascript\"></script>" });
                this.includeAfterScript(this.currentPage.Header);
            } else {
                this.includeBeforeScript(this.currentPage.ClientScript);
                if (!this.currentPage.ClientScript.IsClientScriptIncludeRegistered("fasterFiles")) {
                    if (this.UseAshxExtensions)
                        this.currentPage.ClientScript.RegisterClientScriptInclude("fasterFiles", string.Format((HttpContext.Current.Handler as Page).ResolveUrl("~/ClientFiles.ashx") + "?type=js&id={0}", id));
                    else
                        this.currentPage.ClientScript.RegisterClientScriptInclude("fasterFiles", string.Format("/{0}/FasterFiles." + this.extensionJs, id));
                }
                this.includeAfterScript(this.currentPage.ClientScript);
            }
        }

        private bool checkHandler() {
            if (currentPage == null)
                currentPage = HttpContext.Current.Handler as Page;
            return currentPage != null;
        }

        private static string decodeUtf8(string s_Input) {
            Encoding iso = Encoding.GetEncoding("ISO-8859-2");
            Encoding utf8 = Encoding.UTF8;
            byte[] utfBytes = utf8.GetBytes(s_Input);
            byte[] isoBytes = Encoding.Convert(utf8, iso, utfBytes);
            return iso.GetString(isoBytes);
        }

        private FasterFilesStore getStore() {
            if (!currentPage.Items.Contains("FASTER_FILES")) {
                currentPage.Items.Add("FASTER_FILES", new FasterFilesStore());
                this.currentPage.PreRenderComplete += new EventHandler(currentPage_PreRenderComplete);
            }
            return currentPage.Items["FASTER_FILES"] as FasterFilesStore;
        }

        private List<string> getBeforeScripts() {
            if (checkHandler()) {
                if (!currentPage.Items.Contains("FASTER_FILES_BEFORE")) {
                    currentPage.Items.Add("FASTER_FILES_BEFORE", new List<string>());
                }
                return currentPage.Items["FASTER_FILES_BEFORE"] as List<string>;
            } else {
                return new List<string>();
            }
        }

        private List<string> getAfterScripts() {
            if (checkHandler()) {
                if (!currentPage.Items.Contains("FASTER_FILES_AFTER")) {
                    currentPage.Items.Add("FASTER_FILES_AFTER", new List<string>());
                }
                return currentPage.Items["FASTER_FILES_AFTER"] as List<string>;
            } else {
                return new List<string>();
            }
        }

        public void AddBeforeScript(string script) {
            this.getBeforeScripts().Add(script);
        }

        public void AddAfterScript(string script) {
            this.getAfterScripts().Add(script);
        }

        private string getUniqueID(FasterFilesType type) {
            StringBuilder builder = new StringBuilder();
            IEnumerable<FasterFilesStoreItem> items = this.getStore().Where(t => t.Type == type);
            if (items.Count() == 0) return "";
            foreach (FasterFilesStoreItem item in items) {
                builder.Append(item.SenderType.AssemblyQualifiedName);
                builder.Append(item.File);
            }
            return builder.ToString().GetMd5().ReplaceEx("-", "");
        }

        private void onPreRenderFile(FasterFilesType type, string fileExtension, string commentFormat) {
            string fileHash = this.getUniqueID(type);
            if (type == FasterFilesType.Css)
                this.addCssReference(fileHash);
            if (type == FasterFilesType.JavaScript)
                this.addJsReference(fileHash);
            if (!File.Exists(this.getTempFile(type, fileHash))) {
                FileStream responseFile = File.Open(this.getTempFile(type, fileHash), FileMode.Create);
                StreamWriter writer = new StreamWriter(responseFile);
                writer.Write(getResponse(type, commentFormat));
                writer.Dispose();
                responseFile.Dispose();
            }

        }

        private void currentPage_PreRenderComplete(object sender, EventArgs e) {
            this.onPreRenderFile(FasterFilesType.Css, "css", "/*			{0}			*/");
            this.onPreRenderFile(FasterFilesType.JavaScript, "js", "//	{0}");
        }
        private string getTempFile(FasterFilesType type, string hash) {
            string ext = ".next";
            switch (type) {
                case FasterFilesType.Css:
                    ext = ".css";
                    break;
                case FasterFilesType.JavaScript:
                    ext = ".js";
                    break;
                default:
                    ext = ".next";
                    break;
            }
            string filePath = "";
            try {
                string configTemp = ConfigurationManager.AppSettings["faster_files_temp"];
                if (!string.IsNullOrEmpty(configTemp)) {
                    if (configTemp[0] != '/')
                        configTemp = '/' + configTemp;
                    if (configTemp[configTemp.Length - 1] != '/')
                        configTemp = configTemp + '/';
                    filePath = HttpContext.Current.Server.MapPath(configTemp + hash + ext);
                    if (!new FileInfo(filePath).Directory.Exists)
                        throw new DirectoryNotFoundException();
                }
            } finally {
                if (string.IsNullOrEmpty(filePath))
                    filePath = Path.GetTempPath() + hash + ext;
            }
            return filePath;
        }

        private string getResponse(FasterFilesType type, string commentFormat) {
            StringBuilder buildFile = new StringBuilder();
            foreach (FasterFilesStoreItem item in this.getStore().Where(t => t.Type == type)) {
                buildFile.AppendLine();
                buildFile.AppendFormat(commentFormat, item.File);
                buildFile.AppendLine();
                buildFile.AppendLine();
                writeToOutputStream(item, buildFile);
            }
            return buildFile.ToString();
        }

        private bool isGZipSupported() {
            string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
            if (!string.IsNullOrEmpty(acceptEncoding) &&
                 (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))
                return true;
            return false;
        }

        private void setCacheHeaders(int hashOfResponse) {
            HttpResponse response = HttpContext.Current.Response;
            HttpRequest request = HttpContext.Current.Request;

            response.Cache.VaryByHeaders["Accept-Encoding"] = true;
            response.Cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(7));
            response.Cache.SetMaxAge(new TimeSpan(7, 0, 0, 0));
            response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
            string etag = "\"" + hashOfResponse.ToString() + "\"";
            string incomingEtag = request.Headers["If-None-Match"];
            response.Cache.SetETag(etag);
            if (String.Compare(incomingEtag, etag) == 0) {
                response.Clear();
                response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
                response.SuppressContent = true;
            }
        }

        private void setETags(FasterFilesType type, string id) {
            this.eTag = id;
            this.eTagRequest = HttpContext.Current.Request.Headers["If-None-Match"];

        }

        private void setHeadersForAll(HttpResponse response, string eTag) {
            response.Clear();
            response.Cache.SetCacheability(HttpCacheability.Public);
            response.Cache.SetExpires(DateTime.Now.AddDays(1));
            response.Cache.SetETag(eTag);
        }

        private void setIsNotModified(HttpResponse response) {
            response.StatusCode = 304;
            response.StatusDescription = "Not Modified";
            response.Headers.Add("Content-Length", "0");
        }

        private void trySetGZip() {
            if (isGZipSupported()) {
                HttpResponse response = HttpContext.Current.Response;

                string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
                if (acceptEncoding.Contains("gzip")) {
                    response.Filter = new System.IO.Compression.GZipStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "gzip");
                } else {
                    response.Filter = new System.IO.Compression.DeflateStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "deflate");
                }
            }
        }

        private bool trySetNotModified(HttpResponse response) {
            if (this.eTag == this.eTagRequest) {
                this.setIsNotModified(response);
                return true;
            } else return false;
        }

        private static void writeToOutputStream(FasterFilesStoreItem item, StringBuilder response) {
            Assembly assembly = item.SenderType.Assembly;
            Stream resource = assembly.GetManifestResourceStream(item.File);
            if (resource != null) {

                using (StreamReader reader = new StreamReader(resource)) {
                    //string utf8 = reader.ReadToEnd();
                    //string unicode = decodeUtf8(utf8);
                    response.AppendLine(reader.ReadToEnd());
                }

            }
        }

        #endregion

        #endregion
    }
}